﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Diagnostics.Contracts;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;

namespace NetFrameworkExtensions
{

  public enum Justification { Left, Center, Right, Justify }

  [DebuggerStepThrough]
  public static class GeneralExtensions
  {
    public static T GetProtected<T>(this T failValue, Func<T> func, Action<Exception> failAction = null)
    {
      try
      {
        return func();
      }
      catch (Exception ex)
      {
        if (failAction != null)
          failAction(ex);
        return failValue;
      }
    }
    public static bool DoProtected(this Action action, Action<Exception> failAction = null)
    {
      try
      {
        action();
        return true;
      }
      catch (Exception ex)
      {
        if (failAction != null)
          failAction(ex);
        return false;
      }
    }
    public static bool TryProtected(this Action action, IEnumerable<RetryPattern> retryPatterns, Action<IEnumerable<Tuple<RetryPattern, Exception>>> failAction = null)
    {
      var exceptions = new List<Tuple<RetryPattern, Exception>>();
      var retryEnumerator = retryPatterns.GetEnumerator();
      var retryPattern = RetryPattern.SkipDelay;
      var keepTrying = true;
      while (keepTrying)
      {
        try
        {
          action();
          return true;
        }
        catch (Exception ex)
        {
          exceptions.Add(Tuple.Create(retryPattern, ex));
          if (retryEnumerator.MoveNext())
          {
            retryPattern = retryEnumerator.Current ?? RetryPattern.SkipDelay;
            if (retryPattern == RetryPattern.Stop)
            {
              retryPattern = null;
              keepTrying = false;
            }
            else if (retryPattern.RetryDelay != RetryPattern.SkipDelay)
            {
              Thread.Sleep(retryPattern.RetryDelay);
            }
          }
          else
          {
            retryPattern = null;
            keepTrying = false;
          }
        }
      }
      if (failAction != null)
        failAction(exceptions);
      return false;
    }

    public static IEqualityComparer<TChildren> ToChildrenComparer<TBase, TChildren>(this IEqualityComparer<TBase> baseEqualityComparer) where TChildren : TBase
    {
      return new EqualityComparerDownCaster<TBase, TChildren>(baseEqualityComparer);
    }

    public static T InsteadOf<T>(this T defaultValue, Func<T> func)
    {
      try
      {
        return func();
      }
      catch
      {
        return defaultValue;
      }
    }
    public static T InsteadOf<T>(this Func<T> defaultValueFunc, Func<T> func)
    {
      try
      {
        return func();
      }
      catch
      {
        return defaultValueFunc();
      }
    }
    public static bool IfExists<T>(this T target, Action<T> action)
    {
      if (ReferenceEquals(target, null)) return false;
      action(target);
      return true;
    }

    public static TResult If<TResult, T>(T value, Func<T, bool> predicate, Func<T, TResult> ifTrue, Func<T, TResult> ifFalse)
    {
      return predicate(value) ? ifTrue(value) : ifFalse(value);
    }

    public static T ThrowIfNull<T>(this T value) where T : class
    {
      return ThrowIfDefault(value, null);
    }
    public static T ThrowIfDefault<T>(this T value)
    {
      return ThrowIfDefault(value, default(T));
    }
    public static T ThrowIfDefault<T>(this T value, T defaultValue)
    {
      if (Equals(value, defaultValue)) throw new NullReferenceException();
      return value;
    }

    public static double NextDouble(this Random rnd, double min, double max)
    {
      return rnd.NextDouble() * (max - min) + min;
    }

    public static void DoItFirstTime(this Action action, ref bool alreadyDone)
    {
      if (!alreadyDone)
      {
        action();
        alreadyDone = true;
      }
    }
    public static void DoItExclusive(this Action action, ref bool doing)
    {
      if (!doing)
      {
        doing = true;
        try
        {
          action();
        }
        finally
        {
          doing = false;
        }
      }
    }

    public static TimeSpan MeasureTime(this Action action, int times = 1)
    {
      if (action == null) throw new ArgumentNullException("action");
      // take a shortcut here
      if (times <= 0) return TimeSpan.Zero;
      // some diversion tactics to trick the JIT
      action();
      // use a high accuracy time span measuring device
      var watch = new Stopwatch();
      watch.Start();
      // run the action, whatever it is, times times
      for (int i = 0; i < times; i++)
        action();
      watch.Stop();
      // return the average time taken to run the action
      return TimeSpan.FromMilliseconds(watch.Elapsed.TotalMilliseconds / times);
    }

    public static TimeSpan MeasureTime(this Stopwatch watch, Action action, int times = 1)
    {
      if (watch == null) throw new ArgumentNullException("watch");
      if (action == null) throw new ArgumentNullException("action");
      if (times <= 0) return TimeSpan.Zero;
      watch.Reset();
      watch.Start();
      for (int i = 0; i < times; i++)
        action();
      watch.Stop();
      return TimeSpan.FromMilliseconds(watch.Elapsed.TotalMilliseconds / times);
    }

    public static void Swap<T>(ref T value1, ref T value2)
    {
      T temp = value1;
      value1 = value2;
      value2 = temp;
    }
    public static bool Swap<T>(this bool condition, ref T value1, ref T value2)
    {
      if (condition) Swap(ref value1, ref value2);
      return condition;
    }
    public static bool Swap<T>(this Func<bool> condition, ref T value1, ref T value2)
    {
      return condition().Swap(ref value1, ref value2);
    }

    public static void Swap<T>(this IList<T> list, int pos1, int pos2)
    {
      T temp = list[pos1];
      list[pos1] = list[pos2];
      list[pos2] = temp;
    }
    public static bool Swap<T>(this IList<T> list, bool condition, int pos1, int pos2)
    {
      if (condition) list.Swap(pos1, pos2);
      return condition;
    }
    public static bool Swap<T>(this IList<T> list, Func<bool> condition, int pos1, int pos2)
    {
      return list.Swap(condition(), pos1, pos2);
    }

    public static bool InRange(this int value, int? min, int? max)
    {
      if (min.HasValue && min.Value > value) return false;
      if (max.HasValue && max.Value < value) return false;
      return true;
    }
    public static bool InRange(this double value, double? min, double? max)
    {
      if (min.HasValue && min.Value > value) return false;
      if (max.HasValue && max.Value < value) return false;
      return true;
    }

    public static int ToRange(this int value, int? min, int? max)
    {
      if (min.HasValue && min.Value > value) return min.Value;
      if (max.HasValue && max.Value < value) return max.Value;
      return value;
    }
    public static double ToRange(this double value, double? min, double? max)
    {
      if (min.HasValue && min.Value > value) return min.Value;
      if (max.HasValue && max.Value < value) return max.Value;
      return value;
    }

    public static IDictionary<TKey, TValue> Clone<TKey, TValue>(this IDictionary<TKey, TValue> dict)
    {
      var res = new Dictionary<TKey, TValue>();
      dict.ForEach(pair => res.Add(pair.Key, pair.Value));
      return res;
    }
    public static TObject CloneSerializable<TObject>(this TObject serializableObject)
    {
      var serialized = serializableObject.ToBinarySerializable();
      var clone = serialized.FromBinarySerializable();
      return (TObject)clone;
    }
    public static byte[] ToBinarySerializable(this object serializableObject)
    {
      if (serializableObject == null) return null;
      using (var memoryStream = new MemoryStream())
      {
        var formatter = new BinaryFormatter();
        formatter.Serialize(memoryStream, serializableObject);
        return memoryStream.ToArray();
      }
    }
    public static object FromBinarySerializable(this byte[] serializedObject)
    {
      if (serializedObject == null) return null;
      using (var memoryStream = new MemoryStream(serializedObject))
      {
        var formatter = new BinaryFormatter();
        var result = formatter.Deserialize(memoryStream);
        return result;
      }
    }

    public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> dict, IDictionary<TKey, TValue> other, bool overrideKeys = true)
    {
      foreach (var pair in other)
        if (overrideKeys || !dict.ContainsKey(pair.Key))
          dict[pair.Key] = pair.Value;
      return dict;
    }

    public static S[,] CreateOperationMatrix<T1, T2, S>(this T1[] rows, T2[] cols, Func<T1, T2, S> operation)
    {
      S[,] result = new S[rows.Length, cols.Length];
      for (int i = 0; i < rows.Length; i++)
        for (int j = 0; j < cols.Length; j++)
          result[i, j] = operation(rows[i], cols[j]);
      return result;
    }

    /// <summary>
    /// Returns a tuple containning (position, found)
    /// If found == true, then the value was found at given position 
    /// If found == false, then the value should have been found at given position, but it wasn't
    /// endIndex is excluded from the search
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    /// <param name="comparer"></param>
    /// <returns></returns>
    public static Tup<int, bool> SortedSearch<T>(this IList<T> list, T value, IComparer<T> comparer = null, int startIndex = 0, int endIndex = -1)
    {
      Contract.Requires(list != null);
      Contract.Requires(startIndex >= 0);
      Contract.Requires(endIndex == 0 || (endIndex <= list.Count && endIndex >= startIndex));
      
      if (comparer == null) comparer = Comparer<T>.Default;
      int ini = startIndex;
      int fin = endIndex < 0 ? list.Count : endIndex;
      while (ini < fin)
      {
        int mid = (ini + fin) / 2;
        int comp = comparer.Compare(value, list[mid]);
        if (comp == 0)
          return Tup.Create(mid, true);
        if (comp < 0)
          fin = mid;
        else
          ini = mid + 1;
      }
      return Tup.Create(fin, false);
    }

    #region [ Comparer and Comparable extensions ]

    /// <summary>
    /// Determine if the left operand is greater than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &gt; right</returns>
    public static bool GreaterThan<T>(this T left, T right)
      where T : IComparable<T>
    {
      if (ReferenceEquals(left, null))
        return false;

      return left.CompareTo(right) > 0;
    }
    /// <summary>
    /// Determine if the left operand is less than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &lt; right </returns>
    public static bool LessThan<T>(this T left, T right)
      where T : IComparable<T>
    {
      if (ReferenceEquals(left, null))
        return (ReferenceEquals(right, null)) ? false : true;

      return left.CompareTo(right) < 0;
    }
    /// <summary>
    /// Determines if the left operand is greater than or equal
    /// than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &gt;= right</returns>
    public static bool GreaterThanOrEqual<T>(this T left, T right)
      where T : IComparable<T>
    {
      if (ReferenceEquals(left, null))
        return (ReferenceEquals(right, null)) ? true : false;

      return left.CompareTo(right) >= 0;
    }
    /// <summary>
    /// Determines if the left operand is less than or
    /// equal to the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if the left &lt;= right</returns>
    public static bool LessThanOrEqual<T>(this T left, T right)
      where T : IComparable<T>
    {
      if (ReferenceEquals(left, null))
        return true;
      return left.CompareTo(right) <= 0;
    }
    public static T Min<T>(this T left, T right)
      where T : IComparable<T>
    {
      return left.LessThanOrEqual(right) ? left : right;
    }
    public static T Min<T>(this T first, params T[] rest)
      where T : IComparable<T>
    {
      T min = first;
      for (int i = 0; i < rest.Length; i++)
        min = min.Min(rest[i]);
      return min;
    }
    public static int MinPos<T>(this T first, params T[] rest)
      where T : IComparable<T>
    {
      T min = first;
      int pos = 0;
      for (int i = 0; i < rest.Length; i++)
        if (min.GreaterThan(rest[i]))
        {
          min = rest[i];
          pos = i + 1;
        }
      return pos;
    }
    public static T Max<T>(this T left, T right)
      where T : IComparable<T>
    {
      return left.GreaterThanOrEqual(right) ? left : right;
    }
    public static T Max<T>(this T first, params T[] rest)
      where T : IComparable<T>
    {
      T max = first;
      for (int i = 0; i < rest.Length; i++)
        max = max.Max(rest[i]);
      return max;
    }
    public static int MaxPos<T>(this T first, params T[] rest)
      where T : IComparable<T>
    {
      T max = first;
      int pos = 0;
      for (int i = 0; i < rest.Length; i++)
        if (max.LessThan(rest[i]))
        {
          max = rest[i];
          pos = i + 1;
        }
      return pos;
    }

    /// <summary>
    /// Determine if the left operand is greater than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &gt; right</returns>
    public static bool EqualsTo<T>(this IComparer<T> comparer, T left, T right)
    {
      if (ReferenceEquals(left, right))
        return true;

      return (comparer ?? Comparer<T>.Default).Compare(left, right) == 0;
    }
    /// <summary>
    /// Determine if the left operand is less than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &lt; right </returns>
    public static bool NotEqualsTo<T>(this IComparer<T> comparer, T left, T right)
    {
      return !comparer.EqualsTo(left, right);
    }
    /// <summary>
    /// Determine if the left operand is greater than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &gt; right</returns>
    public static bool GreaterThan<T>(this IComparer<T> comparer, T left, T right)
    {
      if (ReferenceEquals(left, null))
        return false;

      return (comparer ?? Comparer<T>.Default).Compare(left, right) > 0;
    }
    /// <summary>
    /// Determine if the left operand is less than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &lt; right </returns>
    public static bool LessThan<T>(this IComparer<T> comparer, T left, T right)
    {
      if (ReferenceEquals(left, null))
        return (ReferenceEquals(right, null)) ? false : true;

      return comparer.Compare(left, right) < 0;
    }
    /// <summary>
    /// Determines if the left operand is greater than or equal
    /// than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &gt;= right</returns>
    public static bool GreaterThanOrEqual<T>(this IComparer<T> comparer, T left, T right)
    {
      if (ReferenceEquals(left, null))
        return (ReferenceEquals(right, null)) ? true : false;

      return comparer.Compare(left, right) >= 0;
    }
    /// <summary>
    /// Determines if the left operand is less than or
    /// equal to the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if the left &lt;= right</returns>
    public static bool LessThanOrEqual<T>(this IComparer<T> comparer, T left, T right)
    {
      if (ReferenceEquals(left, null))
        return true;
      return comparer.Compare(left, right) <= 0;
    }
    public static T Min<T>(this IComparer<T> comparer, T left, T right)
    {
      return comparer.LessThanOrEqual(left, right) ? left : right;
    }
    public static T Min<T>(this IComparer<T> comparer, T first, params T[] rest)
    {
      T min = first;
      for (int i = 0; i < rest.Length; i++)
        min = comparer.Min(min, rest[i]);
      return min;
    }
    public static int MinPos<T>(this IComparer<T> comparer, T first, params T[] rest)
    {
      T min = first;
      int pos = 0;
      for (int i = 0; i < rest.Length; i++)
        if (comparer.GreaterThan(min, rest[i]))
        {
          min = rest[i];
          pos = i + 1;
        }
      return pos;
    }
    public static T Max<T>(this IComparer<T> comparer, T left, T right)
    {
      return comparer.GreaterThanOrEqual(left, right) ? left : right;
    }
    public static T Max<T>(this IComparer<T> comparer, T first, params T[] rest)
    {
      T max = first;
      for (int i = 0; i < rest.Length; i++)
        max = comparer.Max(max, rest[i]);
      return max;
    }
    public static int MaxPos<T>(this IComparer<T> comparer, T first, params T[] rest)
    {
      T max = first;
      int pos = 0;
      for (int i = 0; i < rest.Length; i++)
        if (comparer.LessThan(max, rest[i]))
        {
          max = rest[i];
          pos = i + 1;
        }
      return pos;
    }

    /// <summary>
    /// Determine if the left operand is greater than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &gt; right</returns>
    public static bool EqualsTo<T>(this T left, T right, IComparer<T> comparer)
    {
      return comparer.EqualsTo(left, right);
    }
    /// <summary>
    /// Determine if the left operand is less than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &lt; right </returns>
    public static bool NotEqualsTo<T>(this T left, T right, IComparer<T> comparer)
    {
      return comparer.NotEqualsTo(left, right);
    }
    /// <summary>
    /// Determine if the left operand is greater than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &gt; right</returns>
    public static bool GreaterThan<T>(this T left, T right, IComparer<T> comparer)
    {
      return comparer.GreaterThan(left, right);
    }
    /// <summary>
    /// Determine if the left operand is less than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &lt; right </returns>
    public static bool LessThan<T>(this T left, T right, IComparer<T> comparer)
    {
      return comparer.LessThan(left, right);
    }
    /// <summary>
    /// Determines if the left operand is greater than or equal
    /// than the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if left &gt;= right</returns>
    public static bool GreaterThanOrEqual<T>(this T left, T right, IComparer<T> comparer)
    {
      return comparer.GreaterThanOrEqual(left, right);
    }
    /// <summary>
    /// Determines if the left operand is less than or
    /// equal to the right
    /// </summary>
    /// <typeparam name="T">the type being compared</typeparam>
    /// <param name="left">The left.</param>
    /// <param name="right">The right.</param>
    /// <returns>true if the left &lt;= right</returns>
    public static bool LessThanOrEqual<T>(this T left, T right, IComparer<T> comparer)
    {
      return comparer.LessThanOrEqual(left, right);
    }
    public static T Min<T>(this T left, T right, IComparer<T> comparer)
    {
      return comparer.LessThanOrEqual(left, right) ? left : right;
    }
    public static T Max<T>(this T left, T right, IComparer<T> comparer)
    {
      return comparer.GreaterThanOrEqual(left, right) ? left : right;
    }

    #endregion

    public static void StartTest(this Stopwatch watch, string message, ConsoleColor? foreColor = null, ConsoleColor? backColor = null)
    {
      var oldForeColor = Console.ForegroundColor;
      var oldBackColor = Console.BackgroundColor;
      if (foreColor.HasValue)
        Console.ForegroundColor = foreColor.Value;
      if (backColor.HasValue)
        Console.BackgroundColor = backColor.Value;
      
      Console.Write(message);

      if (foreColor.HasValue)
        Console.ForegroundColor = oldForeColor;
      if (backColor.HasValue)
        Console.BackgroundColor = oldBackColor;

      watch.Restart();
    }
    public static void EndTest(this Stopwatch watch, string message, ConsoleColor? foreColor = null, ConsoleColor? backColor = null)
    {
      watch.Stop();

      var oldForeColor = Console.ForegroundColor;
      var oldBackColor = Console.BackgroundColor;
      if (foreColor.HasValue)
        Console.ForegroundColor = foreColor.Value;
      if (backColor.HasValue)
        Console.BackgroundColor = backColor.Value;
      
      Console.Write(message);
      Console.WriteLine(" ({0})", watch.Elapsed);

      if (foreColor.HasValue)
        Console.ForegroundColor = oldForeColor;
      if (backColor.HasValue)
        Console.BackgroundColor = oldBackColor;
    }
    public static void StartComplexTest(this Stopwatch watch, string message, ConsoleColor? foreColor = null, ConsoleColor? backColor = null)
    {
      var oldForeColor = Console.ForegroundColor;
      var oldBackColor = Console.BackgroundColor;
      if (foreColor.HasValue)
        Console.ForegroundColor = foreColor.Value;
      if (backColor.HasValue)
        Console.BackgroundColor = backColor.Value;

      Console.WriteLine(message);

      if (foreColor.HasValue)
        Console.ForegroundColor = oldForeColor;
      if (backColor.HasValue)
        Console.BackgroundColor = oldBackColor;

      watch.Restart();
    }
    public static void EndComplexTest(this Stopwatch watch, string message, ConsoleColor? foreColor = null, ConsoleColor? backColor = null)
    {
      watch.EndTest(message, foreColor, backColor);
    }

    public static void StartTest(this Stopwatch watch, TextWriter writer, string message)
    {
      writer.Write(message);
      watch.Restart();
    }
    public static void EndTest(this Stopwatch watch, TextWriter writer, string message)
    {
      watch.Stop();
      writer.Write(message);
      writer.WriteLine(" ({0})", watch.Elapsed);
    }

    public static object GetDefaultValue(this Type t)
    {
      if (t.IsValueType && Nullable.GetUnderlyingType(t) == null)
        return Activator.CreateInstance(t);
      return null;
    }

    public static object GetProperty(this object target, string propertyName, params object[] args)
    {
      if (target == null) throw new ArgumentNullException("target");
      if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentNullException("propertyName");
      var type = target.GetType();
      var result = type.InvokeMember(propertyName, BindingFlags.GetProperty, null, target, args);
      return result;
    }
    public static void SetProperty(this object target, string propertyName, object value, params object[] args)
    {
      if (target == null) throw new ArgumentNullException("target");
      if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentNullException("propertyName");
      var type = target.GetType();
      type.InvokeMember(propertyName, BindingFlags.SetProperty, null, target, args.GetAsEmptyIfNull().Concat(value.Singleton()).ToArray());
    }
    public static object CallMethod(this object target, string methodName, params object[] args)
    {
      if (target == null) throw new ArgumentNullException("target");
      if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentNullException("propertyName");
      var type = target.GetType();
      var result = type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, target, args);
      return result;
    }

    public static byte[] ToBinary(this object obj)
    {
      if (obj == null) return null;
      var formatter = new BinaryFormatter();
      using (var stream = new MemoryStream())
      {
        formatter.Serialize(stream, obj);
        return stream.ToArray();
      }
    }
    public static object FromBinary(this byte[] data)
    {
      if (data == null) return null;
      var formatter = new BinaryFormatter();
      using (var stream = new MemoryStream(data))
      {
        return formatter.Deserialize(stream);
      }
    }

    public static IDisposable SaveValue<T>(this T value, Func<T> valueGetter, Action<T> valueSetter)
    {
      return new ValueSaver<T>(value, valueGetter, valueSetter);
    }

    public static bool AreEquals<T>(this T value1, T value2, IEqualityComparer<T> comparer = null)
    {
      comparer = comparer ?? EqualityComparer<T>.Default;
      return comparer.Equals(value1, value2);
    }
    public static bool AreEqualsArray<T>(this T[] array1, T[] array2, IEqualityComparer<T> comparer = null)
    {
      if (array1 == null)
        return array2 == null;
      if (array2 == null)
        return false;
      if (array1.Length != array2.Length)
        return false;

      comparer = comparer ?? EqualityComparer<T>.Default;
      var length = array1.Length;

      for (int i = 0; i < length; i++)
      {
        if (!comparer.Equals(array1[i], array2[i]))
          return false;
      }
      return true;
    }
    public static bool AreEqualsNullable<T>(this T? item1, T? item2, IEqualityComparer<T> comparer = null)
      where T : struct
    {
      return Nullable.Equals(item1, item2);
      //if (item1 == null)
      //  return item2 == null;
      //if (item2 == null)
      //  return false;

      //comparer = comparer ?? EqualityComparer<T>.Default;
      //return comparer.Equals(item1.Value, item2.Value);
    }
  }

  internal class GeneralExtensionsUtils
  {

    internal static readonly Regex a;
    internal static readonly Regex b;
    internal static readonly Regex c;
    internal static readonly Regex d;
    internal static readonly Regex e;
    internal static readonly Regex f;
    internal static readonly Regex g;
    internal static readonly Regex h;
    internal static readonly Regex i;
    internal static readonly Regex SpacedWordsRegex;

    static GeneralExtensionsUtils()
    {
      a = new Regex(@"[\W_]+", RegexOptions.Compiled);
      b = new Regex("(?<keep>[^aeiou])y$", RegexOptions.Compiled);
      c = new Regex("(?<keep>[aeiou]y)$", RegexOptions.Compiled);
      d = new Regex("(?<keep>[sxzh])$", RegexOptions.Compiled);
      e = new Regex("(?<keep>[^sxzhy])$", RegexOptions.Compiled);
      f = new Regex("(?<keep>[^aeiou])ies$", RegexOptions.Compiled);
      g = new Regex("(?<keep>[aeiou]y)s$", RegexOptions.Compiled);
      h = new Regex("(?<keep>[sxzh])es$", RegexOptions.Compiled);
      i = new Regex("(?<keep>[^sxzhy])s$", RegexOptions.Compiled);
      SpacedWordsRegex = new Regex("[A-Z]+[a-z0-9]*", RegexOptions.Compiled);
    }


  }

  public class ValueSaver<T> : IDisposable
  {
    T oldValue;
    Func<T> valueGetter;
    Action<T> valueSetter;

    public ValueSaver(T newValue, Func<T> valueGetter, Action<T> valueSetter)
    {
      Contract.Requires(valueGetter != null);
      Contract.Requires(valueSetter != null);

      oldValue = valueGetter();
      valueSetter(newValue);
    }

    void IDisposable.Dispose()
    {
      if (valueSetter != null)
      {
        valueSetter(oldValue);
        valueSetter = null;
        valueGetter = null;
        GC.SuppressFinalize(this);
      }
    }
  }

  public class RetryPattern : IEquatable<RetryPattern>
  {
    public static readonly RetryPattern SkipDelay = new RetryPattern(TimeSpan.Zero, false);
    public static readonly RetryPattern Stop = new RetryPattern(TimeSpan.Zero, true);

    public RetryPattern() :
      this(TimeSpan.Zero, false)
    {
    }

    public RetryPattern(TimeSpan delay, bool stopTrying = false) 
    {
      if (delay.TotalMilliseconds < 0) delay = TimeSpan.Zero;
      RetryDelay = delay;
      StopTrying = stopTrying;
    }

    public TimeSpan RetryDelay { get; private set; }
    public bool StopTrying { get; private set; }

    public static implicit operator RetryPattern(TimeSpan delay)
    {
      return new RetryPattern(delay);
    }

    public bool Equals(RetryPattern other)
    {
      return other != null &&
        RetryDelay.Equals(other.RetryDelay) &&
        StopTrying.Equals(other.StopTrying);
    }

    public override bool Equals(object obj)
    {
      return Equals(obj as RetryPattern);
    }

    public override int GetHashCode()
    {
      return RetryDelay.GetHashCode() ^ StopTrying.GetHashCode();
    }

    public static bool operator ==(RetryPattern p1, RetryPattern p2)
    {
      return ReferenceEquals(p1, null) && ReferenceEquals(p2, null) || 
        !ReferenceEquals(p1, null) && !ReferenceEquals(p2, null) && p1.Equals(p2);
    }

    public static bool operator !=(RetryPattern p1, RetryPattern p2)
    {
      return !(p1 == p2);
    }

    public override string ToString()
    {
      if (this == SkipDelay) return "Skip delay";
      if (this == Stop) return "Stop trying";
      return string.Format("Delay {0} and retry", RetryDelay);
    }
  }
}
